home
diamond Go Premium
Data Engineering Path  ·  PySpark

RDD - Shuffle Partitions

A Shuffle is the physical movement of data across different worker nodes in a cluster. It is triggered by Wide Transformations (such as reduceByKey, groupByKey, join, and repartition) when Spark needs to group records with the same keys together.

Shuffling is the single most expensive operation in a distributed application. Tuning the number of partitions during a shuffle is critical: too few partitions can cause memory overload, while too many partitions create slow task-scheduling overhead.

This guide provides a detailed exploration of Shuffle Partitions and default parallelism configurations in PySpark, complete with practical code examples.


RDD Parallelism vs. DataFrame Shuffle Partitions

A common point of confusion is the difference between RDD default parallelism and Spark SQL shuffle partitions:

1. spark.sql.shuffle.partitions (Default: 200)

  • API Scope: Spark SQL / DataFrame API only.
  • Behavior: Controls the partition count for shuffles triggered by DataFrame transformations (like df.groupBy().sum()).
  • Note: Changing this value does not affect RDD transformations!

2. spark.default.parallelism

  • API Scope: Core RDD API.
  • Behavior: Controls the default number of partitions for RDDs created by sc.parallelize() or returned by wide RDD transformations (like reduceByKey) when you do not explicitly pass a partition count.
  • Default Values:
    • Local Mode: Number of CPU cores on the local machine.
    • Cluster Mode (YARN/K8s): Total number of cores on all executor nodes combined, or 2 (whichever is larger).

Performance Impacts of Partition Counts

graph TD
    A["Shuffle Partition Count"] --> B["Too Low (e.g. 1 - 5 partitions)"]
    A --> C["Too High (e.g. 10,000 partitions)"]

    B --> B1["Out Of Memory (OOM) errors"]
    B --> B2["Disk Spilling (data doesn't fit in RAM)"]
    B --> B3["Under-utilization (most cores idle)"]

    C --> C1["Massive Task Scheduler overhead"]
    C --> C2["Tiny data files (metadata bloat)"]
    C --> C3["Network saturation (too many connections)"]

    style A fill:#fff3e0,stroke:#e65100,stroke-width:2px;
    style B fill:#ffebee,stroke:#c62828,stroke-width:2px;
    style C fill:#ffebee,stroke:#c62828,stroke-width:2px;

PySpark Code Examples

A. Setting Default RDD Parallelism in the Builder

You can define the default RDD parallelism when initializing the SparkSession using the .config() method:

from pyspark.sql import SparkSession

# 1. Initialize Spark and set default RDD parallelism to exactly 8
spark = SparkSession.builder \
    .appName("Day01 Shuffle Partitions") \
    .master("local[*]") \
    .config("spark.default.parallelism", "8") \
    .getOrCreate()

sc = spark.sparkContext

# 2. Parallelize a list without specifying slices
# Spark will automatically assign the default parallelism of 8
default_rdd = sc.parallelize([1, 2, 3, 4, 5, 6])
print("Default Parallelism Partitions:", default_rdd.getNumPartitions())
# Output: Default Parallelism Partitions: 8

B. Overriding Partition Counts Directly in Transformations

In the RDD API, the most robust way to control shuffle partition counts is to explicitly pass the partition number as an optional argument directly inside the wide transformation.

This is highly recommended in production because different stages of a pipeline require different levels of parallelism depending on data size!

# 1. Input RDD representing user clicks: (UserID, PagesVisited)
clicks_rdd = sc.parallelize([
    ("User1", 5), ("User2", 12), ("User1", 3), 
    ("User3", 8), ("User2", 4), ("User1", 1)
])

# 2. Aggregating values by key
# By default, reduceByKey would use 'spark.default.parallelism' (8)
# Here, we explicitly override it to use exactly 3 partitions for this operation
aggregated_rdd = clicks_rdd.reduceByKey(lambda x, y: x + y, numPartitions=3)

# 3. View the partition count of the resulting RDD
print("
--- Custom Partitioning in Action ---")
print("Aggregated RDD Partition Count:", aggregated_rdd.getNumPartitions()) # 3

# 4. View how data is distributed inside the 3 partition buckets
partitioned_data = aggregated_rdd.glom().collect()
for idx, partition in enumerate(partitioned_data):
    print(f"  Partition {idx}: {partition}")

# Expected Output:
# Aggregated RDD Partition Count: 3
# Data distribution:
# Partition 0: [('User1', 9)]
# Partition 1: [('User2', 16)]
# Partition 2: [('User3', 8)]
# (Note: Elements are hashed and split into exactly 3 partitions)

C. Contrast: Configuring SQL Shuffle Partitions

For reference, let's look at how to verify and change the shuffle partition count for DataFrames (which uses a different config option):

# 1. Query the default SQL Shuffle partition count (usually 200)
default_sql_partitions = spark.conf.get("spark.sql.shuffle.partitions")
print("Default SQL Shuffle Partitions:", default_sql_partitions) # 200

# 2. Change SQL shuffle partitions to 10 for a smaller dataset
spark.conf.set("spark.sql.shuffle.partitions", "10")
updated_sql_partitions = spark.conf.get("spark.sql.shuffle.partitions")
print("Updated SQL Shuffle Partitions:", updated_sql_partitions) # 10

Best Practices for Tuning Partitions

  • Size of Partition: Ideally, each partition of data should contain between 100MB and 200MB of raw data in memory.
  • Calculate Ideal Partitions:
Ideal Partitions = Dataset Size (MB) / 128
  • Cluster Utilization: Ensure the partition count is at least $2\times$ to $4\times$ the total CPU cores in your cluster. This ensures that when a task finishes quickly on one node, other task slices are immediately available to keep all worker cores fully saturated.
Find this content helpful? ☕ Buy me a coffee

Entity Details

Create New Item

celebration
Enjoying the free content?

Create a free account to track your progress and save your place.

Create Free Account
help

Submit Technical Query

Have a question or run into an issue? Describe it below, upload an optional screenshot, and our engineering team will answer it!

image Attach image (optional)

Submit Feedback

build Free Developer Utility Free Tool
gavel

Privacy & Legal Disclaimer

1. Client-Side Browser Processing

All utility tools on DeepEngineerHub (including Image to PDF, Text Formatters, JSON Converters, and Encryptors) execute 100% locally within your client browser using WebAssembly and JavaScript. No uploaded images, text, or documents are transmitted, collected, or stored on remote servers.

2. Limitation of Liability ("As-Is" Provision)

Tools and services are provided free of charge for convenience and educational purposes "as-is" without warranties of any kind. DeepEngineerHub shall not be held liable for any data loss, formatting inconsistencies, or indirect damages resulting from tool usage.

3. Open Source & Third-Party Software

Certain utilities utilize open-source client libraries (such as jsPDF, Mermaid.js, Pyodide) licensed under MIT, Apache, or BSD open licenses. All intellectual property remains with their respective copyright holders.